/*
 * // Bristleback plugin - Copyright (c) 2010 bristleback.googlecode.com
 * // ---------------------------------------------------------------------------
 * // This program is free software; you can redistribute it and/or modify it
 * // under the terms of the GNU Lesser General Public License as published by the
 * // Free Software Foundation; either version 3 of the License, or (at your
 * // option) any later version.
 * // This library is distributed in the hope that it will be useful,
 * // but without any warranty; without even the implied warranty of merchantability
 * // or fitness for a particular purpose.
 * // You should have received a copy of the GNU Lesser General Public License along
 * // with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>.
 * // ---------------------------------------------------------------------------
 */

package pl.bristleback.server.bristle.token;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import pl.bristleback.server.bristle.config.BristleConfiguration;
import pl.bristleback.server.bristle.exceptions.BristleTokenException;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Logic responsible for processing {@link Tokenize} and {@link TokenizeSet} annotations.
 * <p/>
 * <p/>
 * Created on: 2010-12-25 17:14:47 <br/>
 *
 * @author Wojciech Niemiec
 */
public class TokenizeAnnotationProcessor {
  private static Logger log = Logger.getLogger(TokenizeAnnotationProcessor.class.getName());

  private static Map<Class, TokenizerEngine> customTokenizers = new HashMap<Class, TokenizerEngine>();

  public void processTokenizerAnnotations(Field tokenizerField, Tokenizer tokenizer, BristleConfiguration configuration) {
    List<TokenizerWrapper> wrappers = new ArrayList<TokenizerWrapper>();
    Tokenize[] tokenizerAnnotations = getTokenizerAnnotations(tokenizerField);
    if (tokenizerAnnotations == null) {
      return;
    }
    for (Tokenize tokenize : tokenizerAnnotations) {
      TokenizerWrapper tokenizerWrapper = new TokenizerWrapper();
      tokenizerWrapper.setDeepSearch(tokenize.deepSearch());
      tokenizerWrapper.setName(tokenize.name());
      tokenizerWrapper.setParameterNames(tokenize.parameters());
      tokenizerWrapper.setTokenizerEngine(getTokenizer(tokenize, configuration));
      wrappers.add(tokenizerWrapper);
    }
    createTokenizerMaps(tokenizerField, wrappers, tokenizer);
  }

  private void createTokenizerMaps(Field tokenizerField, List<TokenizerWrapper> wrappers, Tokenizer tokenizer) {
    Map<Integer, TokenizerWrapper> wrapperMap = new HashMap<Integer, TokenizerWrapper>();
    Map<String, TokenizerWrapper> namedWrapperMap = new HashMap<String, TokenizerWrapper>();
    for (TokenizerWrapper tokenizerWrapper : wrappers) {
      Integer parametersAmount = tokenizerWrapper.getParameterNames().length;
      String tokenizerName = tokenizerWrapper.getName();
      if (StringUtils.isBlank(tokenizerName)) {
        putIndexedTokenizer(tokenizerField, wrapperMap, tokenizerWrapper, parametersAmount);
      } else {
        putNamedTokenizer(tokenizerField, namedWrapperMap, tokenizerWrapper, tokenizerName);
      }
    }
    tokenizer.setTokenizerMap(wrapperMap);
    tokenizer.setNamedTokenizerMap(namedWrapperMap);
  }

  private void putNamedTokenizer(Field tokenizerField, Map<String, TokenizerWrapper> namedWrapperMap, TokenizerWrapper tokenizerWrapper, String tokenizerName) {
    if (namedWrapperMap.containsKey(tokenizerName)) {
      throw new BristleTokenException("Multiple " + Tokenize.class.getSimpleName()
        + " annotations with the same name: " + tokenizerName + " in class " + tokenizerField.getDeclaringClass().getSimpleName());
    }
    namedWrapperMap.put(tokenizerName, tokenizerWrapper);
  }

  private void putIndexedTokenizer(Field tokenizerField, Map<Integer, TokenizerWrapper> wrapperMap, TokenizerWrapper tokenizerWrapper, Integer parametersAmount) {
    if (wrapperMap.containsKey(parametersAmount)) {
      throw new BristleTokenException("Multiple " + Tokenize.class.getSimpleName()
        + " annotations with the same number of parameters: " + parametersAmount + " in class " + tokenizerField.getDeclaringClass().getSimpleName());
    }
    wrapperMap.put(parametersAmount, tokenizerWrapper);
  }

  private TokenizerEngine getTokenizer(Tokenize tokenize, BristleConfiguration configuration) {
    if (tokenize.engine().equals(TokenizerEngine.class)) {
      return configuration.getDefaultTokenizer();
    }
    if (customTokenizers.containsKey(tokenize.engine())) {
      return customTokenizers.get(tokenize.engine());
    } else {
      TokenizerEngine tokenizerEngine = createTokenizerInstance(tokenize);
      customTokenizers.put(tokenize.engine(), tokenizerEngine);
      return tokenizerEngine;
    }
  }

  private TokenizerEngine createTokenizerInstance(Tokenize tokenize) {
    try {
      return tokenize.engine().newInstance();
    } catch (Exception e) {
      throw new BristleTokenException("Cannot create instance of custom object tokenizer, class: " + tokenize.engine());
    }
  }

  private Tokenize[] getTokenizerAnnotations(Field tokenizerField) {
    TokenizeSet tokenizeSet = tokenizerField.getAnnotation(TokenizeSet.class);
    if (tokenizeSet != null) {
      return tokenizeSet.value();
    }
    Tokenize tokenize = tokenizerField.getAnnotation(Tokenize.class);
    if (tokenize == null) {
      return null;
    } else {
      return new Tokenize[]{tokenize};
    }
  }
}
